
import random
import sys

from amgineirc.model.amgine.engines.clockWorks import ClockWorks
from amgineirc.model.amgine.parts.listBuilder import ByteKeyByteValueListBuilder, ByteKeyAsciiValueListBuilder
from amgineirc.model.amgine.parts.rule import Rule

class Amgine(object):
    def __init__(self):
        # maxes
        self._irc_maxlen = 400
        self._irc_pre_apply_maxlen = 100
        self._dcc_maxlen = 1024 # (2 ** 14) - 100
        self._dcc_pre_apply_maxlen = 200 # ((self._dcc_maxlen / 3) / 1000) * 1000
        self._dcc_file_pre_apply_maxlen = 5120
        self._handshake_maxlen = 200
        self._handshake_pre_apply_maxlen = 25
        # handshake
        self.handshake = self._build_handshake()
        # rules
        self._byte_to_byte_rule_1 = self._build_byte_to_byte_rule() 
        self._byte_to_byte_rule_2 = self._build_byte_to_byte_rule()
        self._handshake_rule_3 = self._build_byte_to_ascii_rule(self._handshake_maxlen)
        self._irc_rule_3 = self._build_byte_to_ascii_rule(self._irc_maxlen)
        self._dcc_rule_3 = self._build_byte_to_ascii_rule(self._dcc_maxlen)
        self._dcc_file_rule_3 = self._build_byte_to_byte_rule()

    # get random copy of rule keys
            
    def get_rule_1_random_keys(self):
        rule = self._byte_to_byte_rule_1
        l = []
        for i in range(len(rule.keys)):
            key_list = rule.keys[i][:]
            random.shuffle(key_list)
            l.append(key_list)
        return l
        
    def get_rule_2_random_keys(self):
        rule = self._byte_to_byte_rule_2
        l = []
        for i in range(len(rule.keys)):
            key_list = rule.keys[i][:]
            random.shuffle(key_list)
            l.append(key_list)
        return l

    # set rule keys
        
    def set_rule_1_keys(self, key_list):
        len_keys = len(key_list) 
        if len_keys == len(self._byte_to_byte_rule_1.keys): 
            rule = self._byte_to_byte_rule_1
            for i in range(len_keys):
                rule.keys[i] = key_list[i]
        
    def set_rule_2_keys(self, key_list):
        len_keys = len(key_list) 
        if len_keys == len(self._byte_to_byte_rule_2.keys): 
            rule = self._byte_to_byte_rule_2
            for i in range(len_keys):
                rule.keys[i] = key_list[i]
                
        
    @property
    def _handshake_ruleset(self):
        return [self._byte_to_byte_rule_1,
                self._byte_to_byte_rule_2,
                self._handshake_rule_3]
        
    @property
    def _irc_ruleset(self):
        return [self._byte_to_byte_rule_1,
                self._byte_to_byte_rule_2,
                self._irc_rule_3]
        
    @property
    def _dcc_ruleset(self):
        return [self._byte_to_byte_rule_1,
                self._byte_to_byte_rule_2,
                self._dcc_rule_3]
        
    @property
    def _dcc_file_ruleset(self):
        return [self._byte_to_byte_rule_1,
                self._byte_to_byte_rule_2,
                self._dcc_file_rule_3]

    @property
    def dcc_pre_apply_maxlen(self):
        return self._dcc_pre_apply_maxlen

    @property
    def irc_pre_apply_maxlen(self):
        return self._irc_pre_apply_maxlen

    def _build_handshake(self):
        list_builder = ByteKeyAsciiValueListBuilder(random.randint(2,3), -1)
        vList = list_builder.get_shuffled_value_list()
        text = ''.join(vList)
        start = self._handshake_pre_apply_maxlen / 4
        index = random.randrange(start, self._handshake_pre_apply_maxlen, 1)
        return text[:index]
        
    # irc send receive

    def apply_irc_send_rules(self, text, encode_string='utf-8'):
        return self._apply_send_rules(self._irc_ruleset, text, encode_string)

    def apply_irc_receive_rules(self, text, encode_string='utf-8'):
        return self._apply_receive_rules(self._irc_ruleset, text, encode_string)

    # dcc send receive
    
    def apply_dcc_send_rules(self, text, encode_string='utf-8'):
        return self._apply_send_rules(self._dcc_ruleset, text, encode_string)

    def apply_dcc_receive_rules(self, text, encode_string='utf-8'):
        return self._apply_receive_rules(self._dcc_ruleset, text, encode_string)

    # handshake send receive
    
    def apply_handshake_send_rules(self, text, encode_string='utf-8'):
        return self._apply_send_rules(self._handshake_ruleset, text, encode_string)

    def apply_handshake_receive_rules(self, text, encode_string='utf-8'):
        return self._apply_receive_rules(self._handshake_ruleset, text, encode_string)

    # dcc file send receive
    
    def apply_dcc_file_send_rules(self, byte_list):
        if isinstance(byte_list, list):
            ba = bytearray(byte_list)
            balist = self._apply_file_send_rules(self._dcc_file_ruleset, ba)
            return balist[0]
        elif isinstance(byte_list, bytearray):
            balist = self._apply_file_send_rules(self._dcc_file_ruleset, byte_list)
            return balist[0]
        else:
            return bytearray()

    def apply_dcc_file_receive_rules(self, byte_list):
        if isinstance(byte_list, list):
            ba = bytearray(byte_list)
            return self._apply_file_receive_rules(self._dcc_file_ruleset, ba)
        elif isinstance(byte_list, bytearray):
            return self._apply_file_receive_rules(self._dcc_file_ruleset, byte_list)
        else:
            return bytearray()

    def _apply_send_rules(self, ruleset, text, encode_string='utf-8'):
        # convert user input text to ascii text for net send
        #   in 3 steps using a different rule for each step.
        # wind returns a list of lists
        cw = ClockWorks()
        # step 1: convert unicode a list of lists of bytesarrays
        balist1 = cw.wind_string_to_bytes(ruleset[0], text, encode_string)
        # balist1 is a list of bytearrays
        # each bytearray in the list represents a line to send over the internet.
        # step 2: convert each single bytearray to another single bytearray
        # the new list of bytearrays is balist2
        balist2 = []
        for i in balist1:
            ba = cw.wind_bytes_to_bytes(ruleset[1], i)
            for j in ba:
                balist2.append(j)
        # balist2 is the list of new bytearrays
        # convert each bytearray to an ascii string for sending over the internet
        # each ascii string is a seperate text message to send over the internet.
        ascii_list = []
        for i in balist2:
            slist = cw.wind_bytes_to_ascii(ruleset[2], i)
            for j in slist:
                ascii_list.append(j)
        # the conversion from unicode to bytes to bytes to ascii is complete
        return ascii_list

    def _apply_receive_rules(self, ruleset, text, decode_string='utf-8'):
        # text is a single text message received over the interweb :)
        #  so convert that text to unicode for user to read
        # unwind returns a single object (string, bytearray)
        # unwind never returns a list
        # return a single decoded unicode string
        cw = ClockWorks()
        ba = cw.unwind_bytes_from_ascii(ruleset[2], text)
        ba = cw.unwind_bytes_from_bytes(ruleset[1], ba)
        return cw.unwind_string_from_bytes(ruleset[0], ba, decode_string)


    def _apply_file_send_rules(self, ruleset, byte_list):
        # byte_list is a list of bytes
        # convert a list of bytes text to a list of one bytearray
        #   in 3 steps using a different ruleset for each step.
        # cw.wind_bytes_to_bytes will return a list of one byte array
        #   because the byte rules have no limit on size
        #   and data is not broken into smaller chunks.
        cw = ClockWorks()
        # step 1: convert a list of bytes to a list of bytesarrays
        #   using ruleset[0]
        balist1 = cw.wind_bytes_to_bytes(ruleset[0], byte_list)
        # step 2: convert a single bytearray to a list of one bytearray
        #   using ruleset[1]
        balist2 = cw.wind_bytes_to_bytes(ruleset[1], balist1[0])
        # step 3: convert a single bytearray to a list of one bytearray
        #   using ruleset[2]
        return cw.wind_bytes_to_bytes(ruleset[2], balist2[0])

    def _apply_file_receive_rules(self, ruleset, bytes):
        # bytes is list of bytes received from a peer
        # cw.unwind_bytes_from_bytes returns a single bytearray
        # return a single bytearray
        cw = ClockWorks()
        ba1 = cw.unwind_bytes_from_bytes(ruleset[2], bytes)
        ba2 = cw.unwind_bytes_from_bytes(ruleset[1], ba1)
        return cw.unwind_bytes_from_bytes(ruleset[0], ba2)

    def _build_byte_to_byte_rule(self):
        """
        this rule convert each byte to another byte.
        """
        # setup the rule for byte keys and ascii values
        # convert bytes to ascii strings
        rule = Rule()
        # the max length of the spun output
        # not used
        rule.spin_length = -1
        # a random amount of noise bytes at the beginning of the spun output
        rule.spin_noise_count = random.randint(1,10)
        # bytes have values 0 - 255 inclusive
        # the keys are those byte values
        # the end of text key is not used not used because spin_length < 0
        rule.eot_key = -1
        
        # build the spinner rules
        # there will be between 10 and 20 spinners
        # the 1st spinner is used for the first byte and then its keys are shifted,
        # the second spinner is used for the next byte and then its keys are shifted,
        # after the last spinner then back to the first spinner
        n_spinners = random.randint(5, 10)
        # for each spinner
        for i in range(0, n_spinners, 1):
            # this spinner's values will be either 2 or 3 ascii letters wide.
            # the values are made from the same list of letters.
            # the list of letters is the amount needed for 3 letter values.
            # it allows for more repitions of the same letters.
            list_builder = ByteKeyByteValueListBuilder()
            kList = list_builder.get_shuffled_key_list()
            vList = list_builder.get_shuffled_value_list()
            # add this spinner's keys and values to the rules
            rule.keys.append(kList)
            rule.values.append(vList)
            # add this spinner's shift distance to the rules
            rule.shifts.append(random.randint(1,2))
        return rule

    def _build_byte_to_ascii_rule(self, maxlen):
        """
        this rule converts each byte to 2 or 3 ascii chars.
        if maxlen > 0 then that size limit is applied to the ascii strings.
        the size limit may result in multiple ascii strings being created.
        """
        # setup the rule for byte keys and ascii values
        # convert bytes to ascii strings
        rule = Rule()
        # the max length of the spun output
        # this is set for IRC which has a max length of 520
        rule.spin_length = maxlen
        # a random amount of noise bytes at the beginning of the spun output
        rule.spin_noise_count = random.randint(1,10)
        # bytes have values 0 - 255 inclusive
        # the keys are those byte values
        # so use 256 for key which marks the end of text
        rule.eot_key = 256
        
        # build the spinner rules
        # there will be between 10 and 20 spinners
        # the 1st spinner is used for the first byte and then its keys are shifted,
        # the second spinner is used for the next byte and then its keys are shifted,
        # after the last spinner then back to the first spinner
        n_spinners = random.randint(5, 10)
        # for each spinner
        for i in range(0, n_spinners, 1):
            # this spinner's values will be either 2 or 3 ascii letters wide.
            # the values are made from the same list of letters.
            # the list of letters is the amount needed for 3 letter values.
            # it allows for more repitions of the same letters.
            list_builder = ByteKeyAsciiValueListBuilder(random.randint(2,3), rule.eot_key)
            kList = list_builder.get_shuffled_key_list()
            vList = list_builder.get_shuffled_value_list()
            # add this spinner's keys and values to the rules
            rule.keys.append(kList)
            rule.values.append(vList)
            # add this spinner's shift distance to the rules
            rule.shifts.append(random.randint(1,2))
        return rule
